/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef PANDA_PRODUCT_BUILD

#include "plugins/ecmascript/runtime/base/builtins_base.h"
namespace ark::ecmascript::builtins {
// A set of bits returned by GetOptimizationStatus.
enum class OptimizationStatus {
    IS_FUNCTION = 1U << 0U,      // NOLINT(readability-magic-numbers)
    NEVER_OPTIMIZE = 1U << 1U,   // NOLINT(readability-magic-numbers)
    ALWAYS_OPTIMIZE = 1U << 2U,  // NOLINT(readability-magic-numbers)
    MAYBE_DEOPTED = 1U << 3U,    // NOLINT(readability-magic-numbers)
    OPTIMIZED = 1U << 4U,        // NOLINT(readability-magic-numbers)

    INTERPRETED = 1U << 7U,                         // NOLINT(readability-magic-numbers)
    MARKED_FOR_OPTIMIZATION = 1U << 8U,             // NOLINT(readability-magic-numbers)
    MARKED_FOR_CONCURRENT_OPTIMIZATION = 1U << 9U,  // NOLINT(readability-magic-numbers)
    OPTIMIZING_CONCURRENTLY = 1U << 10U,            // NOLINT(readability-magic-numbers)
    IS_EXECUTING = 1U << 11U,                       // NOLINT(readability-magic-numbers)
    TOPMOST_FRAME_IS_COMPILED = 1U << 12U,          // NOLINT(readability-magic-numbers)
    LITE_MODE = 1U << 13U,                          // NOLINT(readability-magic-numbers)
    MARKED_FOR_DEOPTIMIZATION = 1U << 14U,          // NOLINT(readability-magic-numbers)

    TOPMOST_FRAME_IS_INTERPRETED = 1U << 16U,  // NOLINT(readability-magic-numbers)

    IS_LAZY = 1U << 18U,  // NOLINT(readability-magic-numbers)
};

JSTaggedValue runtime_testing::GetOptimizationStatus(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv != nullptr);
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, GetOptimizationStatus);
    JSHandle<JSTaggedValue> argTagged = builtins_common::GetCallArg(argv, 0);

    uint32_t status = 0;

    const auto &opts = Runtime::GetCurrent()->GetOptions();
    bool noAsyncJit = Runtime::GetCurrent()->GetPandaVM()->GetCompiler()->IsNoAsyncJit();

    if (!opts.IsCompilerEnableJit()) {
        status |= static_cast<uint32_t>(OptimizationStatus::LITE_MODE);
        status |= static_cast<uint32_t>(OptimizationStatus::NEVER_OPTIMIZE);
    } else if ((opts.GetCompilerHotnessThreshold() == 0) && noAsyncJit) {
        status |= static_cast<uint32_t>(OptimizationStatus::ALWAYS_OPTIMIZE);
    }
    if (argTagged->IsCallable()) {
        return JSTaggedValue(status);
    }

    ASSERT(argTagged->IsCallable());
    const auto *method = JSHandle<ECMAObject>::Cast(argTagged)->GetCallTarget();
    status |= static_cast<uint32_t>(OptimizationStatus::IS_FUNCTION);

    switch (method->GetCompilationStatus()) {
        case Method::NOT_COMPILED: {
            if (method->IsDestroyed()) {
                status |= static_cast<uint32_t>(OptimizationStatus::MARKED_FOR_DEOPTIMIZATION);
            }
            status |= static_cast<uint32_t>(OptimizationStatus::INTERPRETED);
            break;
        }
        case Method::WAITING: {
            if (noAsyncJit) {
                status |= static_cast<uint32_t>(OptimizationStatus::MARKED_FOR_OPTIMIZATION);
            } else {
                status |= static_cast<uint32_t>(OptimizationStatus::MARKED_FOR_CONCURRENT_OPTIMIZATION);
            }
            break;
        }
        case Method::COMPILATION: {
            status |= static_cast<uint32_t>(OptimizationStatus::OPTIMIZING_CONCURRENTLY);
            break;
        }
        case Method::COMPILED: {
            status |= static_cast<uint32_t>(OptimizationStatus::OPTIMIZING_CONCURRENTLY);
            break;
        }
        case Method::FAILED: {
            LOG(FATAL, RUNTIME) << "Compilation failure";
        }
    }
    auto stack = StackWalker::Create(thread);
    for (; stack.HasFrame(); stack.NextFrame()) {
        if (stack.GetMethod() == method) {
            status |= static_cast<uint32_t>(OptimizationStatus::IS_EXECUTING);
            if (stack.IsCFrame()) {
                status |= static_cast<uint32_t>(OptimizationStatus::TOPMOST_FRAME_IS_COMPILED);
            } else {
                status |= static_cast<uint32_t>(OptimizationStatus::TOPMOST_FRAME_IS_INTERPRETED);
            }
            break;
        }
    }

    // NOTE(dkofanov): implement --compiler-deopt-every-n-times
    ASSERT((status & static_cast<uint32_t>(OptimizationStatus::MAYBE_DEOPTED)) == 0);

    return JSTaggedValue(status);
}

JSTaggedValue runtime_testing::PrepareFunctionForOptimization(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv != nullptr);
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, PrepareFunctionForOptimization);
    JSHandle<JSTaggedValue> argTagged = builtins_common::GetCallArg(argv, 0);

    ASSERT(argTagged->IsCallable());
    auto *jsMethod = JSHandle<ECMAObject>::Cast(argTagged)->GetCallTarget();
    ASSERT(!jsMethod->IsNative());
    if (jsMethod->GetProfilingVector() == nullptr) {
        jsMethod->InitProfileVector();
        ASSERT(jsMethod->GetProfilingVector() != nullptr);
    }
    return JSTaggedValue::Undefined();
}

JSTaggedValue runtime_testing::OptimizeFunctionOnNextCall(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv != nullptr);
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, OptimizeFunctionOnNextCall);
    JSHandle<JSTaggedValue> argTagged0 = builtins_common::GetCallArg(argv, 0);
    JSHandle<JSTaggedValue> argTagged1 = builtins_common::GetCallArg(argv, 1);

    ASSERT(argTagged0->IsCallable());
    auto *jsMethod = JSHandle<ECMAObject>::Cast(argTagged0)->GetCallTarget();
    ASSERT(jsMethod->GetProfilingVector() != nullptr);

    bool isAsync = false;
    if (!argTagged1->IsUndefined()) {
        ASSERT(argTagged1->IsString());
        constexpr size_t STR_LEN = 10;
        std::array<uint8_t, STR_LEN + 1> modeConcurrent = {'c', 'o', 'n', 'c', 'u', 'r', 'r', 'e', 'n', 't', '\0'};
        if (EcmaString::StringsAreEqualUtf8(EcmaString::ConstCast(argTagged1->GetTaggedObject()), modeConcurrent.data(),
                                            STR_LEN, true)) {
            isAsync = true;
        } else {
            THROW_TYPE_ERROR_AND_RETURN(thread, "mode is expected to be 'concurrent' or undefined",
                                        JSTaggedValue::Exception());
        }
    }
    auto *compiler = Runtime::GetCurrent()->GetPandaVM()->GetCompiler();
    if (jsMethod->HasCompiledCode()) {
        return JSTaggedValue::Undefined();
    }
    bool savedAsyncFlag = compiler->IsNoAsyncJit();
    compiler->SetNoAsyncJit(!isAsync);
    compiler->CompileMethod(jsMethod, 0, false, argTagged0.GetTaggedValue());
    ASSERT(isAsync || jsMethod->HasCompiledCode());
    compiler->SetNoAsyncJit(savedAsyncFlag);
    return JSTaggedValue::Undefined();
}

JSTaggedValue runtime_testing::Exit(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv != nullptr);
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, Exit);
    JSHandle<JSTaggedValue> argTagged = builtins_common::GetCallArg(argv, 0);

    ASSERT(argTagged->IsInt());
    Runtime::Halt(argTagged->GetInt());
    UNREACHABLE();
}

// Not implemented
JSTaggedValue runtime_testing::IsBeingInterpreted([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, IsBeingInterpreted);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::DeoptimizeFunction([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, DeoptimizeFunction);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::NeverOptimizeFunction([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, NeverOptimizeFunction);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::DeoptimizeNow([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, DeoptimizeNow);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
// NOLINTNEXLINE(readability-identifier-naming)
JSTaggedValue runtime_testing::_DeoptimizeNow([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, _DeoptimizeNow);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::ClearFunctionFeedback([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, ClearFunctionFeedback);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::OptimizeOsr([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, OptimizeOsr);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::CompleteInobjectSlackTracking([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, CompleteInobjectSlackTracking);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::IsDictPropertyConstTrackingEnbled([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, IsDictPropertyConstTrackingEnbled);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::StringMaxLength([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, StringMaxLength);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::TypedArrayMaxLength([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, TypedArrayMaxLength);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::DisableOptimizationFinalization([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, DisableOptimizationFinalization);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::WaitForBackgroundOptimization([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, WaitForBackgroundOptimization);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::FinalizeOptimization([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, FinalizeOptimization);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::ArrayBufferDetach([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, ArrayBufferDetach);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::EnsureFeedbackVectorForFunction([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, EnsureFeedbackVectorForFunction);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::SimulateNewspaceFull([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, SimulateNewspaceFull);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::VerifyType([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, VerifyType);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::ToFastProperties([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, ToFastProperties);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::IsConcurrentRecompilationSupported([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, IsConcurrentRecompilationSupported);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::GetUndetectable([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, GetUndetectable);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::ToLength([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, ToLength);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::RunningInSimulator([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, RunningInSimulator);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::AllocateHeapNumber([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, AllocateHeapNumber);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::PretenureAllocationSite([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, PretenureAllocationSite);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::MaxSmi([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, MaxSmi);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::CreatePrivateSymbol([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, CreatePrivateSymbol);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::CreatePrivateNameSymbol([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, CreatePrivateNameSymbol);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}

// Not implemented
JSTaggedValue runtime_testing::Is64Bit([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, RuntimeTesting, Is64Bit);
    THROW_TYPE_ERROR_AND_RETURN(argv->GetThread(), "Builtin is not implemented", JSTaggedValue::Exception());
}
}  // namespace ark::ecmascript::builtins

#endif  // PANDA_PRODUCT_BUILD
